package kablamo;

import java.io.IOException;
import java.util.ArrayList;

import kablamo.SensorRequestData.SensorDataObject;

import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.FilterChainEvent;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.attributes.Attribute;
import org.glassfish.grizzly.attributes.AttributeBuilder;

import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Connection;


public class SensorCodecFilter extends BaseFilter {
	
	private final Attribute<SensorContextState> sensorContextAttrib;

	private MemoryManager memoryManager;

	public SensorCodecFilter() {
		this.sensorContextAttrib = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute("SensorCodecFilter.SensorContext");
		
	}
	
	
	public NextAction handleEvent(FilterChainContext ctx, FilterChainEvent event) 
		throws IOException {
		
		if(event instanceof SensorCodecStateTransitionEvent) {
			
			final Connection connection = ctx.getConnection();
			SensorContextState sensorContext = sensorContextAttrib.get(connection);
			if(sensorContext == null) {
				sensorContext = new SensorContextState(connection);
				sensorContextAttrib.set(connection, sensorContext);
			} 
			
			SensorCodecStateTransitionEvent sensorCodecStateTransitionEvent = (SensorCodecStateTransitionEvent) event;
			sensorContext.setAuthorized(sensorCodecStateTransitionEvent.success);
			
			byte [] byteRepresentation = new byte[2];
			if(sensorCodecStateTransitionEvent.success == true) {
				byteRepresentation[0] = 0x01;
			}
			else {
				byteRepresentation[0] = 0x00;
			}
			
			byteRepresentation[1] = 0x00;
			
			final Buffer output =
                ctx.getConnection().getTransport().getMemoryManager().allocate(2);
			//output.putInt((int)342342343);
			output.put(byteRepresentation);
			output.flip();
			ctx.write(output);
			
		}
		
		return ctx.getStopAction();
	}
	
	@Override
	public NextAction handleRead(FilterChainContext ctx)
		throws IOException {
		
			final Buffer input = (Buffer) ctx.getMessage();
			final Connection connection = ctx.getConnection();
	
			SensorContextState sensorContext = sensorContextAttrib.get(connection);
			if(sensorContext == null) {
				sensorContext = new SensorContextState(connection);
				sensorContextAttrib.set(connection, sensorContext);
			} 
			if(sensorContext.getAuthorized() == false) { 
				if(input.remaining() < 12) {
					return ctx.getStopAction();
				}
				else {
					SensorRequestInit sensorRequestInit = new SensorRequestInit();
					sensorRequestInit.accessKey = input.getInt();
					sensorRequestInit.deviceKey = input.getInt();
					sensorRequestInit.timeStamp = (long) (input.getInt() & 0xffffffffL);
					
					ctx.setMessage(sensorRequestInit);
					return ctx.getInvokeAction();
				}
			}
			else {
				if(input.remaining() < 6) {
					return ctx.getStopAction(input);
				}
				else {
					if(sensorContext.getCurrentDataPoints() == 0) {
						sensorContext.setCurrentTimeStamp((long) (input.getInt() & 0xffffffffL));
						sensorContext.setCurrentDataPoints((int) (input.getShort() & 0xffff));						
					}

					if(input.remaining() < sensorContext.getCurrentDataPoints() * 12) {
						return ctx.getStopAction(input);
					}
					else {
						
						
						SensorRequestData sensorRequestData = new SensorRequestData();
						sensorRequestData.masterTimeStamp = sensorContext.getCurrentTimeStamp();
						sensorRequestData.dataElements = new ArrayList<SensorDataObject>();
						
						// We send sensor data as a increasing sequence, if any packet is corrupt we simply grab
						// the sequence number it should have and request retransmission. 
						
						ArrayList<Short> retransmitRequestSequenceList = new ArrayList<Short>();
						int sequenceNumber = 0;
						
						for(int i = 0; i < sensorContext.getCurrentDataPoints(); i++) {
							input.mark();
							
							input.get();
							input.reset();
							// These are all public fields of a POJO.
							SensorDataObject sensorDataObject = sensorRequestData.new SensorDataObject();
							sensorDataObject.sequenceNumber = ((int)input.getShort() & 0xffff);
							sensorDataObject.sensorId = ((int)input.get() & 0xff);
							int checksum = ((int)input.get() & 0xff);
							sensorDataObject.timeStampDelta = ((long)input.getInt() & 0xffffffffL);
							sensorDataObject.sensorData = (Float.intBitsToFloat(input.getInt()));
							sensorRequestData.dataElements.add(sensorDataObject);
							
							// Reset the buffer to beginning of entry, re-read. 
							input.reset();
							int verificationSum = 0;
							for(int j = 0; j < 12; j++) {
								// read in an unsigned byte and add to checksum. 
								byte val = input.get();
								if(j != 3) verificationSum += (val & 0xff);	
							}
							
							// this means something really odd happened and the packet needs to be retransmitted. 
							if((verificationSum & 0xff) != checksum) {
								retransmitRequestSequenceList.add((short)sequenceNumber);
							}
							
							// debug test sequence corruption.
							//if(sequenceNumber == 1)
							//	retransmitRequestSequenceList.add((short)sequenceNumber);
							
							sequenceNumber++;
						}
								
						final Buffer output =
			                ctx.getConnection().getTransport().getMemoryManager().allocateAtLeast(1);
						
						if(retransmitRequestSequenceList.size() > 0) {
							output.put((byte) 0);
							output.putShort((short) retransmitRequestSequenceList.size());
							for ( Short i : retransmitRequestSequenceList) {					
								output.putShort(i);
							}
						}
						else {
							output.put((byte) 1);
						}
						output.flip();
						ctx.write(output);
						
						// This serves as a flag to tell us we're not waiting for sensor data, but waiting for the next data header.
						sensorContext.setCurrentDataPoints(0);
						ctx.setMessage(sensorRequestData);
						return ctx.getInvokeAction();
					}
				}	
			}
		}
	
	
	@Override
	public NextAction handleWrite(final FilterChainContext ctx) throws IOException {
		
		return ctx.getInvokeAction();
	}
	
		
	}